为什么还需要 TypeORM
Prisma 在类型安全和开发体验上非常优秀,但在某些极端的关系型数据库场景中,TypeORM 的兼容性更好——尤其是涉及复杂关联查询、存储过程、视图等高级特性时。通用模板项目需要覆盖尽可能多的数据库场景,因此两种方案都需要集成。
安装依赖
pnpm add @nestjs/typeorm typeorm mysql2
bash
| 包名 | 作用 |
|---|---|
@nestjs/typeorm | NestJS TypeORM 集成模块 |
typeorm | TypeORM 核心 |
mysql2 | MySQL 数据库驱动(按实际数据库替换为 pg、sqlite3 等) |
异步配置(forRootAsync)
TypeORM 支持两种配置方式:forRoot(硬编码)和 forRootAsync(异步读取配置)。生产项目必须使用 forRootAsync,从 .env 读取数据库连接参数。
环境变量
将 Prisma 的单一 DATABASE_URL 拆解为独立参数,便于 TypeORM 逐项读取:
# .env
DB_TYPE=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USERNAME=root
DB_PASSWORD=example
DB_DATABASE=testdb
DB_SYNC=false
DB_LOAD=false
bash
AppModule 配置
// app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigService } from '@nestjs/config';
@Module({
imports: [
TypeOrmModule.forRootAsync({
inject: [ConfigService],
useFactory: (configService: ConfigService) =>
({
type: configService.get<string>('DB_TYPE', 'mysql') as 'mysql',
host: configService.get<string>('DB_HOST', '127.0.0.1'),
port: configService.get<number>('DB_PORT', 3306),
username: configService.get<string>('DB_USERNAME', 'root'),
password: configService.get<string>('DB_PASSWORD', ''),
database: configService.get<string>('DB_DATABASE', 'testdb'),
synchronize: configService.get<boolean>('DB_SYNC', false),
autoLoadEntities: configService.get<boolean>('DB_LOAD', false),
}) as TypeOrmModule.TypeOrmModuleOptions,
}),
],
})
export class AppModule {}
typescript
关键配置项说明:
| 参数 | 默认值 | 说明 |
|---|---|---|
type | mysql | 数据库类型,支持 mysql、postgres、sqlite、mssql 等 |
synchronize | false | 启动时自动同步实体到数据库(生产禁止开启) |
autoLoadEntities | false | 自动加载通过 forFeature() 注册的实体 |
configService.get()返回的布尔值可能是字符串,需要用Boolean()或=== 'true'转换。
类型断言
useFactory 的返回值需要断言为 TypeOrmModule.TypeOrmModuleOptions,否则 TypeScript 会报类型不兼容错误。
实体定义
TypeORM 使用装饰器(Decorator)定义实体类,与 TypeScript 的类语法天然契合:
// src/user/user.entity.ts
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
username: string;
@Column()
password: string;
}
typescript
| 装饰器 | 说明 |
|---|---|
@Entity() | 标记类为数据库实体 |
@PrimaryGeneratedColumn() | 自增主键 |
@Column() | 普通列 |
@Column({ nullable: true }) | 允许为空 |
@Column({ unique: true }) | 唯一约束 |
@Column({ length: 100 }) | 指定长度 |
与 Prisma 的 .prisma Schema 文件不同,TypeORM 直接用 TypeScript 类定义实体,无需额外的代码生成步骤。
forFeature 注册实体
在需要使用实体的模块中,通过 forFeature 注册实体并获取对应的 Repository:
// app.module.ts
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user/user.entity';
@Module({
imports: [
// ... forRootAsync 配置
TypeOrmModule.forFeature([User]),
],
})
export class AppModule {}
typescript
autoLoadEntities: true 时,forFeature 注册的实体会被自动收集,无需手动指定 entities 数组。
Repository 注入与使用
在 Controller 中注入
// app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user/user.entity';
@Controller()
export class AppController {
constructor(
@InjectRepository(User)
private readonly userRepository: Repository<User>,
) {}
@Get()
async findAll(): Promise<User[]> {
return this.userRepository.find();
}
}
typescript
@InjectRepository(User) 是 NestJS 提供的装饰器,用于注入 TypeORM 的 Repository 实例。
常用 Repository 方法
| 方法 | 说明 | 示例 |
|---|---|---|
find() | 查询所有 | repository.find() |
findOneBy({ id }) | 按 ID 查询 | repository.findOneBy({ id: 1 }) |
create(dto) | 创建实体(未保存) | repository.create({ username: 'admin' }) |
save(entity) | 保存实体 | repository.save(user) |
merge(entity, dto) | 合并更新 | repository.merge(user, { name: 'new' }) |
delete(id) | 删除 | repository.delete(1) |
常见错误排查
ConfigService 导入错误
Error: ConfigService is not defined / Cannot find module 'config'
text
原因:导入路径写错。应该从 @nestjs/config 导入 ConfigService,而非其他路径。
// 正确
import { ConfigService } from '@nestjs/config';
// 错误
import { ConfigService } from 'config';
typescript
synchronize 的危险
synchronize: true 在应用启动时自动同步实体定义到数据库。如果删除了实体中的某个字段,该列会被直接删除——数据不可恢复。
.env 配置:DB_SYNC=false(生产环境必须关闭)
text
开发阶段可以临时开启 DB_SYNC=true,但在 CI/CD 和生产环境中务必为 false,使用 migration 管理数据库变更。
Prisma vs TypeORM 集成对比
| 维度 | Prisma | TypeORM |
|---|---|---|
| 模型定义 | .prisma 文件(独立 DSL) | TypeScript 装饰器实体类 |
| 类型生成 | prisma generate 自动生成 | 天然 TypeScript 类型 |
| 模块注册 | 自定义 PrismaService | @nestjs/typeorm 官方包 |
| 数据操作 | prisma.user.findMany() | repository.find() |
| 配置方式 | DATABASE_URL 连接字符串 | 逐项参数配置 |
| 代码生成 | 需要手动运行 generate | 无需额外步骤 |
| 数据库同步 | 显式 db push / migrate | synchronize(可自动) |
两种方案的集成结果一致——都能在 Controller 中查询到用户数据。下一节将集成 Mongoose(MongoDB ODM),完成三套数据库方案的覆盖。
↑